{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/husein/.local/lib/python3.6/site-packages/sklearn/cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.\n",
      "  \"This module will be removed in 0.20.\", DeprecationWarning)\n"
     ]
    }
   ],
   "source": [
    "from utils import *\n",
    "import tensorflow as tf\n",
    "from sklearn.cross_validation import train_test_split\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['negative', 'positive']\n",
      "10662\n",
      "10662\n"
     ]
    }
   ],
   "source": [
    "trainset = sklearn.datasets.load_files(container_path = 'data', encoding = 'UTF-8')\n",
    "trainset.data, trainset.target = separate_dataset(trainset,1.0)\n",
    "print (trainset.target_names)\n",
    "print (len(trainset.data))\n",
    "print (len(trainset.target))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "vocab from size: 20332\n",
      "Most common words [('film', 1453), ('movie', 1270), ('one', 727), ('like', 721), ('story', 477), ('much', 386)]\n",
      "Sample data [170, 45, 5347, 10158, 76, 3661, 534, 1785, 897, 7006] ['fascinating', 'look', 'israel', 'ferment', 'feels', 'immediate', 'latest', 'news', 'footage', 'gaza']\n"
     ]
    }
   ],
   "source": [
    "concat = ' '.join(trainset.data).split()\n",
    "vocabulary_size = len(list(set(concat)))\n",
    "data, count, dictionary, rev_dictionary = build_dataset(concat, vocabulary_size)\n",
    "print('vocab from size: %d'%(vocabulary_size))\n",
    "print('Most common words', count[4:10])\n",
    "print('Sample data', data[:10], [rev_dictionary[i] for i in data[:10]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "GO = dictionary['GO']\n",
    "PAD = dictionary['PAD']\n",
    "EOS = dictionary['EOS']\n",
    "UNK = dictionary['UNK']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "embedding_size = 256\n",
    "maxlen = 100\n",
    "batch_size = 16\n",
    "learning_rate = 1e-3\n",
    "num_layers = 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numbers\n",
    "from tensorflow.contrib.layers import xavier_initializer\n",
    "from tensorflow.python.framework import tensor_shape\n",
    "from tensorflow.python.framework import tensor_util\n",
    "from tensorflow.python.ops import array_ops\n",
    "from tensorflow.python.ops import random_ops\n",
    "from tensorflow.python.ops import math_ops\n",
    "from tensorflow.python.framework import ops\n",
    "\n",
    "\n",
    "def zoneout(x, keep_prob, noise_shape = None, seed = None, name = None):\n",
    "    with tf.name_scope(name or 'dropout') as name:\n",
    "        x = ops.convert_to_tensor(x, name = 'x')\n",
    "        if isinstance(keep_prob, numbers.Real) and not 0 < keep_prob <= 1:\n",
    "            raise ValueError(\n",
    "                'keep_prob must be a scalar tensor or a float in the '\n",
    "                'range (0, 1], got %g' % keep_prob\n",
    "            )\n",
    "        keep_prob = ops.convert_to_tensor(\n",
    "            keep_prob, dtype = x.dtype, name = 'keep_prob'\n",
    "        )\n",
    "        keep_prob.get_shape().assert_is_compatible_with(tensor_shape.scalar())\n",
    "        if tensor_util.constant_value(keep_prob) == 1:\n",
    "            return x\n",
    "\n",
    "        noise_shape = (\n",
    "            noise_shape if noise_shape is not None else array_ops.shape(x)\n",
    "        )\n",
    "        random_tensor = keep_prob\n",
    "        random_tensor += random_ops.random_uniform(\n",
    "            noise_shape, seed = seed, dtype = x.dtype\n",
    "        )\n",
    "        binary_tensor = math_ops.floor(random_tensor)\n",
    "        ret = x * binary_tensor\n",
    "        ret.set_shape(x.get_shape())\n",
    "        return 1.0 - ret\n",
    "\n",
    "\n",
    "class QRNN_pooling(tf.nn.rnn_cell.RNNCell):\n",
    "    def __init__(self, out_fmaps):\n",
    "        self.__out_fmaps = out_fmaps\n",
    "\n",
    "    @property\n",
    "    def state_size(self):\n",
    "        return self.__out_fmaps\n",
    "\n",
    "    @property\n",
    "    def output_size(self):\n",
    "        return self.__out_fmaps\n",
    "\n",
    "    def __call__(self, inputs, state, scope = None):\n",
    "        with tf.variable_scope(scope or 'QRNN-pooling'):\n",
    "            Z, F, O = tf.split(inputs, 3, 1)\n",
    "            new_state = tf.multiply(F, state) + tf.multiply(\n",
    "                tf.subtract(1.0, F), Z\n",
    "            )\n",
    "            output = tf.multiply(O, new_state)\n",
    "            return output, new_state\n",
    "\n",
    "\n",
    "class QRNN_layer(object):\n",
    "    \"\"\" Quasi-Recurrent Neural Network Layer\n",
    "        (cf. https://arxiv.org/abs/1611.01576)\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self,\n",
    "        out_fmaps,\n",
    "        fwidth = 2,\n",
    "        activation = tf.tanh,\n",
    "        pool_type = 'fo',\n",
    "        zoneout = 0.1,\n",
    "        infer = False,\n",
    "        bias_init_val = None,\n",
    "        name = 'QRNN',\n",
    "    ):\n",
    "        self.out_fmaps = out_fmaps\n",
    "        self.activation = activation\n",
    "        self.name = name\n",
    "        self.pool_type = pool_type\n",
    "        self.fwidth = fwidth\n",
    "        self.out_fmaps = out_fmaps\n",
    "        self.zoneout = zoneout\n",
    "        self.bias_init_val = bias_init_val\n",
    "\n",
    "    def __call__(self, input_):\n",
    "        input_shape = input_.get_shape().as_list()\n",
    "        batch_size = tf.shape(input_)[0]\n",
    "        fwidth = self.fwidth\n",
    "        out_fmaps = self.out_fmaps\n",
    "        zoneout = self.zoneout\n",
    "        with tf.variable_scope(self.name):\n",
    "            Z, gates = self.convolution(input_, fwidth, out_fmaps, zoneout)\n",
    "            T = tf.concat([Z] + gates, 2)\n",
    "            pooling = QRNN_pooling(out_fmaps)\n",
    "            self.initial_state = pooling.zero_state(\n",
    "                batch_size = batch_size, dtype = tf.float32\n",
    "            )\n",
    "            H, last_C = tf.nn.dynamic_rnn(\n",
    "                pooling, T, initial_state = self.initial_state\n",
    "            )\n",
    "            self.Z = Z\n",
    "            return H, last_C\n",
    "\n",
    "    def convolution(self, input_, filter_width, out_fmaps, zoneout_):\n",
    "        in_shape = input_.get_shape()\n",
    "        in_fmaps = in_shape[-1]\n",
    "        num_gates = num_layers\n",
    "        gates = []\n",
    "        pinput = tf.pad(input_, [[0, 0], [filter_width - 1, 0], [0, 0]])\n",
    "        with tf.variable_scope('convolutions'):\n",
    "            Wz = tf.get_variable(\n",
    "                'Wz',\n",
    "                [filter_width, in_fmaps, out_fmaps],\n",
    "                initializer = tf.random_uniform_initializer(\n",
    "                    minval = -0.05, maxval = 0.05\n",
    "                ),\n",
    "            )\n",
    "            z_a = tf.nn.conv1d(pinput, Wz, stride = 1, padding = 'VALID')\n",
    "            if self.bias_init_val is not None:\n",
    "                bz = tf.get_variable(\n",
    "                    'bz',\n",
    "                    [out_fmaps],\n",
    "                    initializer = tf.constant_initializer(0.0),\n",
    "                )\n",
    "                z_a += bz\n",
    "\n",
    "            z = self.activation(z_a)\n",
    "            for gate_name in range(num_gates):\n",
    "                Wg = tf.get_variable(\n",
    "                    'W{}'.format(gate_name),\n",
    "                    [filter_width, in_fmaps, out_fmaps],\n",
    "                    initializer = tf.random_uniform_initializer(\n",
    "                        minval = -0.05, maxval = 0.05\n",
    "                    ),\n",
    "                )\n",
    "                g_a = tf.nn.conv1d(pinput, Wg, stride = 1, padding = 'VALID')\n",
    "                if self.bias_init_val is not None:\n",
    "                    bg = tf.get_variable(\n",
    "                        'b{}'.format(gate_name),\n",
    "                        [out_fmaps],\n",
    "                        initializer = tf.constant_initializer(0.0),\n",
    "                    )\n",
    "                    g_a += bg\n",
    "                g = tf.sigmoid(g_a)\n",
    "                gates.append(g)\n",
    "        return z, gates"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Model:\n",
    "    def __init__(self):\n",
    "        self.X = tf.placeholder(tf.int32, [None, maxlen])\n",
    "        self.Y = tf.placeholder(tf.int32, [None])\n",
    "\n",
    "        self.initial_states = []\n",
    "        self.last_states = []\n",
    "        self.qrnns = []\n",
    "        with tf.variable_scope('QRNN_LM'):\n",
    "            word_W = tf.get_variable(\n",
    "                'word_W',\n",
    "                [len(dictionary), embedding_size],\n",
    "                initializer = tf.random_uniform_initializer(\n",
    "                    minval = -0.05, maxval = 0.05\n",
    "                ),\n",
    "            )\n",
    "            words = tf.split(1, maxlen, tf.expand_dims(self.X, -1))\n",
    "            embeddings = tf.nn.embedding_lookup(word_W, self.X)\n",
    "\n",
    "            qrnn_h = embeddings\n",
    "            for qrnn_l in range(num_layers):\n",
    "                qrnn_ = QRNN_layer(\n",
    "                    embedding_size,\n",
    "                    pool_type = 'fo',\n",
    "                    zoneout = 0.1,\n",
    "                    name = 'QRNN_layer{}'.format(qrnn_l),\n",
    "                )\n",
    "                qrnn_h, last_state = qrnn_(qrnn_h)\n",
    "                self.last_states.append(last_state)\n",
    "                self.initial_states.append(qrnn_.initial_state)\n",
    "                self.qrnns.append(qrnn_)\n",
    "        self.logits = tf.layers.dense(qrnn_h[:, -1], len(trainset.target))\n",
    "\n",
    "        self.cost = tf.reduce_mean(\n",
    "            tf.nn.sparse_softmax_cross_entropy_with_logits(\n",
    "                logits = self.logits, labels = self.Y\n",
    "            )\n",
    "        )\n",
    "        self.optimizer = tf.train.AdamOptimizer(learning_rate).minimize(\n",
    "            self.cost\n",
    "        )\n",
    "        correct_pred = tf.equal(\n",
    "            tf.argmax(self.logits, 1, output_type = tf.int32), self.Y\n",
    "        )\n",
    "        self.accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.reset_default_graph()\n",
    "sess = tf.InteractiveSession()\n",
    "model = Model()\n",
    "sess.run(tf.global_variables_initializer())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "vectors = str_idx(trainset.data,dictionary,maxlen)\n",
    "train_X, test_X, train_Y, test_Y = train_test_split(vectors, trainset.target,test_size = 0.2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:22<00:00,  4.02it/s, accuracy=0, cost=0.791]    \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:19<00:00,  6.90it/s, accuracy=0.4, cost=0.695]  \n",
      "train minibatch loop:   0%|          | 1/534 [00:00<01:38,  5.42it/s, accuracy=0.375, cost=0.709]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 0, pass acc: 0.000000, current acc: 0.480263\n",
      "time taken: 162.2818534374237\n",
      "epoch: 0, training loss: 1.378447, training acc: 0.495603, valid loss: 0.796239, valid acc: 0.480263\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:27<00:00,  6.43it/s, accuracy=0, cost=1.65]     \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:20<00:00,  6.51it/s, accuracy=0.8, cost=0.788]  \n",
      "train minibatch loop:   0%|          | 1/534 [00:00<01:29,  5.93it/s, accuracy=0.812, cost=0.543]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 1, pass acc: 0.480263, current acc: 0.562025\n",
      "time taken: 168.59028959274292\n",
      "epoch: 1, training loss: 0.695151, training acc: 0.591277, valid loss: 0.730916, valid acc: 0.562025\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:19<00:00,  4.40it/s, accuracy=0, cost=0.822]    \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:18<00:00,  5.66it/s, accuracy=0.8, cost=0.729]  \n",
      "train minibatch loop:   0%|          | 0/534 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 2, pass acc: 0.562025, current acc: 0.623910\n",
      "time taken: 158.36772322654724\n",
      "epoch: 2, training loss: 0.507770, training acc: 0.757299, valid loss: 0.796808, valid acc: 0.623910\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:23<00:00,  3.80it/s, accuracy=1, cost=0.00447]   \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:17<00:00,  8.92it/s, accuracy=1, cost=0.114]    \n",
      "train minibatch loop:   0%|          | 1/534 [00:00<01:18,  6.82it/s, accuracy=1, cost=0.0963]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 3, pass acc: 0.623910, current acc: 0.661510\n",
      "time taken: 160.69531559944153\n",
      "epoch: 3, training loss: 0.270673, training acc: 0.893774, valid loss: 0.925024, valid acc: 0.661510\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:26<00:00,  2.29it/s, accuracy=1, cost=0.000573]  \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:18<00:00,  4.20it/s, accuracy=1, cost=0.101]    \n",
      "train minibatch loop:   0%|          | 0/534 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 4, pass acc: 0.661510, current acc: 0.667604\n",
      "time taken: 164.61770606040955\n",
      "epoch: 4, training loss: 0.096986, training acc: 0.968578, valid loss: 1.097514, valid acc: 0.667604\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:23<00:00,  6.64it/s, accuracy=1, cost=6.89e-5]   \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:19<00:00,  6.87it/s, accuracy=0.8, cost=0.157]  \n",
      "train minibatch loop:   0%|          | 1/534 [00:00<01:20,  6.59it/s, accuracy=1, cost=0.0128]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "time taken: 162.6290693283081\n",
      "epoch: 5, training loss: 0.033695, training acc: 0.992027, valid loss: 1.306623, valid acc: 0.660947\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:25<00:00,  5.56it/s, accuracy=1, cost=3.52e-5]   \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:19<00:00,  4.29it/s, accuracy=0.8, cost=0.205]  \n",
      "train minibatch loop:   0%|          | 0/534 [00:00<?, ?it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "time taken: 165.59291863441467\n",
      "epoch: 6, training loss: 0.011736, training acc: 0.999179, valid loss: 1.466664, valid acc: 0.662353\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "train minibatch loop: 100%|██████████| 534/534 [02:27<00:00,  2.86it/s, accuracy=1, cost=1.28e-5]   \n",
      "test minibatch loop: 100%|██████████| 134/134 [00:18<00:00,  8.82it/s, accuracy=0.8, cost=0.412]  "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "time taken: 166.14567828178406\n",
      "epoch: 7, training loss: 0.004355, training acc: 1.000938, valid loss: 1.697881, valid acc: 0.667511\n",
      "\n",
      "break epoch:8\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "from tqdm import tqdm\n",
    "import time\n",
    "\n",
    "EARLY_STOPPING, CURRENT_CHECKPOINT, CURRENT_ACC, EPOCH = 3, 0, 0, 0\n",
    "\n",
    "while True:\n",
    "    lasttime = time.time()\n",
    "    if CURRENT_CHECKPOINT == EARLY_STOPPING:\n",
    "        print('break epoch:%d\\n' % (EPOCH))\n",
    "        break\n",
    "\n",
    "    train_acc, train_loss, test_acc, test_loss = 0, 0, 0, 0\n",
    "    pbar = tqdm(\n",
    "        range(0, len(train_X), batch_size), desc = 'train minibatch loop'\n",
    "    )\n",
    "    for i in pbar:\n",
    "        batch_x = train_X[i : min(i + batch_size, train_X.shape[0])]\n",
    "        batch_y = train_Y[i : min(i + batch_size, train_X.shape[0])]\n",
    "        acc, cost, _ = sess.run(\n",
    "            [model.accuracy, model.cost, model.optimizer],\n",
    "            feed_dict = {\n",
    "                model.Y: batch_y,\n",
    "                model.X: batch_x\n",
    "            },\n",
    "        )\n",
    "        assert not np.isnan(cost)\n",
    "        train_loss += cost\n",
    "        train_acc += acc\n",
    "        pbar.set_postfix(cost = cost, accuracy = acc)\n",
    "        \n",
    "    pbar = tqdm(range(0, len(test_X), batch_size), desc = 'test minibatch loop')\n",
    "    for i in pbar:\n",
    "        batch_x = test_X[i : min(i + batch_size, test_X.shape[0])]\n",
    "        batch_y = test_Y[i : min(i + batch_size, test_X.shape[0])]\n",
    "        acc, cost = sess.run(\n",
    "            [model.accuracy, model.cost],\n",
    "            feed_dict = {\n",
    "                model.Y: batch_y,\n",
    "                model.X: batch_x\n",
    "            },\n",
    "        )\n",
    "        test_loss += cost\n",
    "        test_acc += acc\n",
    "        pbar.set_postfix(cost = cost, accuracy = acc)\n",
    "\n",
    "    train_loss /= len(train_X) / batch_size\n",
    "    train_acc /= len(train_X) / batch_size\n",
    "    test_loss /= len(test_X) / batch_size\n",
    "    test_acc /= len(test_X) / batch_size\n",
    "\n",
    "    if test_acc > CURRENT_ACC:\n",
    "        print(\n",
    "            'epoch: %d, pass acc: %f, current acc: %f'\n",
    "            % (EPOCH, CURRENT_ACC, test_acc)\n",
    "        )\n",
    "        CURRENT_ACC = test_acc\n",
    "        CURRENT_CHECKPOINT = 0\n",
    "    else:\n",
    "        CURRENT_CHECKPOINT += 1\n",
    "\n",
    "    print('time taken:', time.time() - lasttime)\n",
    "    print(\n",
    "        'epoch: %d, training loss: %f, training acc: %f, valid loss: %f, valid acc: %f\\n'\n",
    "        % (EPOCH, train_loss, train_acc, test_loss, test_acc)\n",
    "    )\n",
    "    EPOCH += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "validation minibatch loop: 100%|██████████| 134/134 [00:17<00:00,  4.29it/s]\n"
     ]
    }
   ],
   "source": [
    "real_Y, predict_Y = [], []\n",
    "\n",
    "pbar = tqdm(\n",
    "    range(0, len(test_X), batch_size), desc = 'validation minibatch loop'\n",
    ")\n",
    "for i in pbar:\n",
    "    batch_x = test_X[i : min(i + batch_size, test_X.shape[0])]\n",
    "    batch_y = test_Y[i : min(i + batch_size, test_X.shape[0])]\n",
    "    predict_Y += np.argmax(\n",
    "        sess.run(\n",
    "            model.logits, feed_dict = {model.X: batch_x, model.Y: batch_y}\n",
    "        ),\n",
    "        1,\n",
    "    ).tolist()\n",
    "    real_Y += batch_y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "             precision    recall  f1-score   support\n",
      "\n",
      "   negative       0.67      0.70      0.68      1108\n",
      "   positive       0.66      0.63      0.64      1025\n",
      "\n",
      "avg / total       0.66      0.66      0.66      2133\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(metrics.classification_report(real_Y, predict_Y, target_names = trainset.target_names))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
